#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define LCD_WIDTH    800
#define LCD_HEIGHT   480
#define NUM_PICTURES 9

typedef struct                          //bmp文件的文件头信息
{
    unsigned short bf_type;             //文件类型，如果是bmp文件，值为“BM”
    unsigned int   bf_size;             //文件总大小
    unsigned short bf_reserved1;        //保留字1，一般为0
    unsigned short bf_reserved2;        //保留字2，一般为0
    unsigned int   bf_offbits;          //文件起始位置到像素数据的距离
}__attribute__((packed)) bmp_file_header;

typedef struct                          //bmp文件的信息头信息
{
    unsigned int   bi_size;             //信息头大小
    unsigned int   bi_width;            //图像宽度
    unsigned int   bi_height;           //图像高度
    unsigned short bi_planes;           //色彩平面，必须为1
    unsigned short bi_bitcount;         //位深度
    unsigned int   bi_compression;      //是否压缩，一般为0
    unsigned int   bi_sizeimage;        //图像颜色信息占用的实际字节数，包括了对齐所需的0
    unsigned int   bi_x_pels_per_meter; //水平分辨率
    unsigned int   bi_y_pels_per_meter; //垂直分辨率
    unsigned int   bi_clr_used;         //调色板中颜色数量，一般为0（不表示没有颜色）
    unsigned int   bi_clr_important;    //重要颜色的数量一般为0（表示每种颜色都重要）
}__attribute__((packed)) bmp_info_header;

bmp_file_header file_header[NUM_PICTURES];      //全局结构体变量
bmp_info_header info_header[NUM_PICTURES];

int white_color = 0x00ffffff;                   //白色
int black_color = 0x00000000;                   //黑色

/***对文件头、信息头结构体变量进行初始化***/
/***
 * 参数：void
 * 返回值：void
 * ***/
void my_struct_init(void)
{
    int i = 0;

    for (i = 0; i < NUM_PICTURES; i++)
    {
        memset(&file_header[i], 0, sizeof(bmp_file_header));
        memset(&info_header[i], 0, sizeof(bmp_info_header));
    }
}


/***对LCD屏幕的某一像素进行颜色rgb赋值***/
/***
 * 参数：
 * x,y：该像素在LCD屏幕的位置坐标值
 * color：该像素所要显示的颜色
 * lcd_ptr：操作LCD屏幕的指针
 * ***/
void lcd_draw_point(int x, int y, int color, int *lcd_ptr)
{
    if(x < 0 || x >= LCD_WIDTH || y < 0 || y >= LCD_HEIGHT)
    {
        printf("drawing point outside of screen.");
        exit(1);
    }

    *(lcd_ptr + y * LCD_WIDTH + x) = color;
}


/***初始化LCD屏幕背景颜色***/
void blackground_init(int color, int *lcd_ptr)
{
    int cur_x = 0;
    int cur_y = 0;

    while(cur_y < LCD_HEIGHT)
    {
        while(cur_x < LCD_WIDTH)
        {
            lcd_draw_point(cur_x, cur_y, color, lcd_ptr);

            cur_x++;
        }
        cur_x = 0;
        cur_y++;
    }
}


/***读取bmp文件的文件头、信息头数据***/
/***
 * 参数：void
 * 返回值：void
 * ***/
void get_header_information(int fd[])
{
    int i = 0;
    int ret = 0;

    for (i = 0; i < NUM_PICTURES; i++)
    {
        ret = read(fd[i], &file_header[i], sizeof(bmp_file_header));            //读取文件头信息
        if(ret < 0)
        {
            printf("read file header %d failed\n", i);
            exit(1);
        }

        ret = read(fd[i], &info_header[i], sizeof(bmp_info_header));            //读取信息头信息
        if(ret < 0)
        {
            printf("read info header %d failed\n", i);
            exit(1);
        }

        printf("%d: %u %u %u\n", i, file_header[i].bf_offbits, info_header[i].bi_height, info_header[i].bi_width);

    }
}


/***在LCD屏幕显示任意大小的图片，但必须为4的倍数***/
/***
 * 参数：
 * fd：图片文件的文件描述符
 * offset：图片显示起始位置的偏移量，offset[0][0]代表第一张图片在横轴方向x的偏移量，offset[0][1]代表第一张图片在纵轴方向y的偏移量
 * lcd_ptr：操作LCD屏幕的指针
 * 返回值：void
 * ***/
void show_pictures_on_lcd(int fd, int offset_x, int offset_y, int* lcd_ptr)
{
    int ret = 0;
    int x = 0;
    int y = 0;
    static int n = 0;
 
    char* bmp_buf = (char*)malloc(sizeof(char) * info_header[n].bi_height * info_header[n].bi_width * 3);

    memset(bmp_buf, 0, info_header[n].bi_height * info_header[n].bi_width * 3);                             //对保存bmp图片像素数据的缓冲区清零
    
    ret = read(fd, bmp_buf, info_header[n].bi_height * info_header[n].bi_width * 3);                        //读取bmp图片的像素数据

    if(ret < 0)
    {
        printf("read bmp file %d failed\n", n);
        exit(1);
    }

    ret = lseek(fd, file_header[n].bf_offbits, SEEK_SET);                              //将读指针设置在像素数据开始位置，为下一个循环做准备
    if (ret < 0)
    {
        printf("re lseek bmp file %d failed\n", n);
        exit(1);
    }

    for(y = offset_y; y < offset_y + info_header[n].bi_height; y++)                     //LCD显示图片
    {
        for(x = offset_x; x < offset_x + info_header[n].bi_width; x++)
        {
            lcd_ptr[(LCD_HEIGHT - 1 - y) * LCD_WIDTH + x] = bmp_buf[((y - offset_y) * info_header[n].bi_width + x - offset_x) * 3 + 0]
                                                            | (bmp_buf[((y - offset_y) * info_header[n].bi_width + x - offset_x) * 3 + 1] << 8)
                                                            | (bmp_buf[((y - offset_y) * info_header[n].bi_width + x - offset_x) * 3 + 2] << 16);
        }
    }

    free(bmp_buf);                                             //释放动态创建的内存空间
    bmp_buf = NULL;
    
    n = ++n%NUM_PICTURES;                                      //下一张图片

    sleep(1);                                                  //延时1s
    
}



int main()
{
    char* path_name[NUM_PICTURES] = {"kobe.bmp",                //文件路径名称
                                     "kobe1.bmp",
                                     "kobe2.bmp",
                                     "kobe3.bmp",
                                     "kobe4.bmp",
                                     "kobe5.bmp",
                                     "kobe6.bmp",
                                     "kobe7.bmp",
                                     "kobe8.bmp"};
    int bmp_offset[NUM_PICTURES][2] = {0, 0,                   //设置图片在LCD显示时的起始位置
                                       0, 0,
                                       0, 0,
                                       0, 0,
                                       20, 247,
                                       280, 247,
                                       540, 247,
                                       27, 13,
                                       413, 13};       

    int bmp_fd[NUM_PICTURES] = {0};                             //存取bmp文件的文件描述符
    int lcd_fd = 0;                                             //LCD文件的文件描述符
    int ret = 0;                                                //存取返回值
    
    int i = 0;                                                  //循环变量

    int* mylcd_ptr = NULL;                                      //LCD屏幕操作指针

    for(i = 0; i < NUM_PICTURES; i++)                           //打开图片文件
    {
        bmp_fd[i] = open(path_name[i], O_RDONLY);
        if (bmp_fd[i] < 0)
        {
            printf("open bmp file %d failed\n", i);
            return 1;
        }
    }

    lcd_fd = open("/dev/fb0", O_RDWR);                          //打开LCD屏幕文件
    if (lcd_fd < 0)
    {
        perror("open lcd file failed");
        return 1;
    }

                                                                //LCD文件的内存映射
    mylcd_ptr = (int*)mmap(NULL, LCD_HEIGHT * LCD_WIDTH * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);

    my_struct_init();                                           //结构体变量初始化
    get_header_information(bmp_fd);                             //读取文件头、信息头信息

    i = 0;
    while(i < NUM_PICTURES)                                     //循环播放图片
    {
        show_pictures_on_lcd(bmp_fd[i], bmp_offset[i][0], bmp_offset[i][1], mylcd_ptr);        //显示图片
        i++;
        if (NUM_PICTURES == i)                                     //下一个周期循环
        {
            i = 0;
        }
        if(4 == i)                                                 //背景颜色初始化
        {
            blackground_init(black_color, mylcd_ptr);
        } 
    }
    

    munmap(mylcd_ptr, LCD_HEIGHT * LCD_WIDTH * 4);              //释放内存映射

    for(i = 0; i < NUM_PICTURES; i++)                           //关闭图片文件
    {
        ret = close(bmp_fd[i]);
        if (ret < 0)
        {
            printf("close bmp file %d failed\n", i);
            return 1;
        }
    }

    
    ret = close(lcd_fd);                                       //关闭LCD屏幕文件
    if (ret < 0)
    {
        perror("close lcd file failed");
        return 1;
    }
    

    return 0;
}
